﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using EnterpriseDataAccess.DataProviders;

namespace EnterpriseDataAccess.Process
{
    sealed class DBEngine : IDBEngine, IDisposable
    {
        private DbConnection idbConnection;
        private DbDataReader idataReader;
        private DbCommand idbCommand;
        private Server server;
        private DbTransaction idbTransaction = null;
        private DbParameter[] idbParameters = null;
        private string strConnection;

        private void AttachParameters(DbCommand command, DbParameter[]commandParameters)
         {
            foreach (DbParameter idbParameter in commandParameters)
            {
              if ((idbParameter.Direction == ParameterDirection.InputOutput) && (idbParameter.Value == null))
              {
                idbParameter.Value = DBNull.Value;
              }
             command.Parameters.Add(idbParameter);
           }
         }
  
        private void PrepareCommand(DbCommand command, DbConnection connection, DbTransaction transaction, CommandType commandType, string commandText, DbParameter[]commandParameters)
         {
            command.Connection = connection;
            command.CommandText = commandText;
            command.CommandType = commandType;
  
            if (transaction != null)
            {
              command.Transaction = transaction;
            }
  
            if (commandParameters != null)
            {
              AttachParameters(command, commandParameters);
            }
         }

        public DBEngine()
        {
        }

        public DBEngine(Server server)
        {
            this.server = server;
        }

        public DBEngine(Server server, string connectionString)
        {
            this.server = server;
            this.strConnection = connectionString;
        }

        public Server ServerType
        {
            get
            {
                return server;
            }
            set
            {
                server = value;
            }
        }

        public string ConnectionString
        {
            get
            {
                return strConnection;
            }
            set
            {
                strConnection = value;
            }
        }

        public DbConnection Connection
        {
            get { return idbConnection; }
        }

        public DbTransaction Transaction
        {
            get { return idbTransaction; }
        }

        public DbDataReader DataReader
        {
            get
            {
                return idataReader;
            }
            set
            {
                idataReader = value;
            }
        }

        public DbCommand Command
        {
            get { return idbCommand; }
        }

        public DbParameter[] Parameters
        {
            get { return idbParameters; }
        }

        public void Open()
        {
            idbConnection = DBEngineFactory.GetConnection(this.server);
            idbConnection.ConnectionString = this.ConnectionString;
            if (idbConnection.State != ConnectionState.Open)
                idbConnection.Open();
            this.idbCommand = DBEngineFactory.GetCommand(this.server);
        }

        public void BeginTransaction()
        {
            if (this.idbTransaction == null)
                idbTransaction = DBEngineFactory.GetTransaction(this.idbConnection);
            this.idbCommand.Transaction = idbTransaction;
        }

        public void CommitTransaction()
        {
            if (this.idbTransaction != null)
                this.idbTransaction.Commit();
            idbTransaction = null;
        }

        public void RollBack()
        {
            if (this.idbTransaction != null)
                this.idbTransaction.Rollback();
            idbTransaction = null;
        }

        public void CreateParameters(int paramsCount)
        {
            idbParameters = new DbParameter[paramsCount];
            idbParameters = DBEngineFactory.GetParameters(this.server, paramsCount);
        }

        public void AddParameters(int index, string paramName, object objValue)
        {
            if (index < idbParameters.Length)
            {
                idbParameters[index].ParameterName = paramName;
                idbParameters[index].Value = objValue;
            }
        }

        public DbDataReader ExecuteReader(CommandType commandType, string commandText)
        {
            this.idbCommand = DBEngineFactory.GetCommand(this.server);
            idbCommand.Connection = this.Connection;
            PrepareCommand(idbCommand, this.Connection, this.Transaction, commandType, commandText, this.Parameters);
            this.DataReader = idbCommand.ExecuteReader();
            idbCommand.Parameters.Clear();
            return this.DataReader;
        }

        public DataSet ExecuteDataSet(CommandType commandType, string commandText)
        {
            this.idbCommand = DBEngineFactory.GetCommand(this.server);
            idbCommand.Connection = this.Connection;
            PrepareCommand(idbCommand, this.Connection, this.Transaction, commandType, commandText, this.Parameters);
            DbDataAdapter dataAdapter = DBEngineFactory.GetDataAdapter(this.server);
            dataAdapter.SelectCommand = idbCommand;
            DataSet dataSet = new DataSet();
            dataAdapter.Fill(dataSet);
            idbCommand.Parameters.Clear();
            return dataSet;
        }

        public DataTable ExecuteDataTable(CommandType commandType, string commandText)
        {
            this.idbCommand = DBEngineFactory.GetCommand(this.server);
            idbCommand.Connection = this.Connection;
            PrepareCommand(idbCommand, this.Connection, this.Transaction, commandType, commandText, this.Parameters);
            DbDataAdapter dataAdapter = DBEngineFactory.GetDataAdapter(this.server);
            dataAdapter.SelectCommand = idbCommand;
            DataTable dataSet = new DataTable();
            dataAdapter.Fill(dataSet);
            idbCommand.Parameters.Clear();
            return dataSet;
        }

        public object ExecuteScalar(CommandType commandType, string commandText)
        {
            this.idbCommand = DBEngineFactory.GetCommand(this.server);
            idbCommand.Connection = this.Connection;
            PrepareCommand(idbCommand, this.Connection, this.Transaction, commandType, commandText, this.Parameters);
            object returnValue = idbCommand.ExecuteScalar();
            idbCommand.Parameters.Clear();
            return returnValue;
        }

        public int ExecuteNonQuery(CommandType commandType, string commandText)
        {
            this.idbCommand = DBEngineFactory.GetCommand(this.server);
            idbCommand.Connection = this.Connection;
            PrepareCommand(idbCommand, this.Connection, this.Transaction, commandType, commandText, this.Parameters);
            int returnValue = idbCommand.ExecuteNonQuery();
            idbCommand.Parameters.Clear();
            return returnValue;
        }

        public void CloseReader()
        {
            if (this.DataReader != null)
                this.DataReader.Close();
        }

        public void Close()
        {
            if (idbConnection.State != ConnectionState.Closed)
                idbConnection.Close();
        }

        public void Dispose()
        {
            GC.Collect();
            GC.SuppressFinalize(this);
            this.Close();
            this.idbCommand = null;
            this.idbTransaction = null;
            this.idbConnection = null;
        }

        public void CreateParameters(Dictionary<string, object> parameters)
        {
            this.CreateParameters(parameters.Count);
            int index = 0;
            foreach (var p in parameters)
            {
                this.AddParameters(index, p.Key, p.Value);
                index += 1;
            }
        }


        public void CreateParameters(DataRow row)
        {
            DataTable dt = row.Table;
            this.CreateParameters(dt.Columns.Count);

            int index = 0;

            if (row.RowState == DataRowState.Deleted)
            {
                foreach (DataColumn c in dt.Columns)
                {
                    this.AddParameters(index, "@" + c.ColumnName, row[c.ColumnName, DataRowVersion.Original]);
                    index += 1;
                }
            }
            else
            {
                foreach (DataColumn c in dt.Columns)
                {
                    this.AddParameters(index, "@" + c.ColumnName, row[c.ColumnName]);
                    index += 1;
                }
            }
        }


        public void FillDataTable(ref DataTable table, CommandType commandType, string commandText)
        {
            this.idbCommand = DBEngineFactory.GetCommand(this.server);
            idbCommand.Connection = this.Connection;
            PrepareCommand(idbCommand, this.Connection, this.Transaction, commandType, commandText, this.Parameters);
            DbDataAdapter dataAdapter = DBEngineFactory.GetDataAdapter(this.server);
            dataAdapter.SelectCommand = idbCommand;
            dataAdapter.Fill(table);
            idbCommand.Parameters.Clear();
        }
    }
}
